package mobile.TheOldRepublic.web;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import mobile.TheOldRepublic.util.Constants;

import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.ContentNode;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.HtmlNode;
import org.htmlcleaner.PrettyHtmlSerializer;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.Utils;

import com.explodingzombie.util.GeneralUtils;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class WebViewClientWrapper extends WebViewClient {
	
	private static final int PROGRESS_TIMEOUT_MS = 5000;
	
	private Activity activity = null;
	int width = Constants.WEB_DEFAULT_WIDTH;
	private String contextRoot = null;
	private ArrayList<String> specialTags = new ArrayList<String>(10);
	private ProgressDialog pd = null;
	protected LinkedList<HistoryContainer> history = new LinkedList<HistoryContainer>();
	private WebView webview = null;
	
	private boolean hasBeenOverridden = false;
	
	private static final short REPLACE = 2;
	private static final short KEEP = 1;
	private static final short UNKNOWN = 0;
	private static final short REMOVE = -1;
	private static final short FORCE_REMOVE = -2;
	
	// Debug
	private String lastHtml = null;
	
	private boolean shouldOverride(String urlString){
		boolean override = false;
		for(String od : Constants.OVERRIDE_DOMAINS)
			if(urlString.toLowerCase().contains(od)){
				override = true;
				break;
			}
		
		return override;
	}
	
	@Override
	public boolean shouldOverrideUrlLoading(WebView view, String urlString){
		if(GeneralUtils.isEmpty(urlString)) return false;
		webview = view;
		
		boolean override = shouldOverride(urlString);
		
		final String url = urlString;
		if(override){
			Log.d(this.getClass().getSimpleName(), "Loading Intercepted");
			Toast.makeText(activity, "Loading Intercepted", Toast.LENGTH_SHORT).show();
			startProgress();

			if(urlString.toLowerCase().contains(Constants.FORUM_URL))
				specialTags.add("spacer");
			// Because this is running in the same Thread as the UI, the spinner wasn't being
			// drawn until after shouldOverrideUrlLoading had completed.
			Thread t = new Thread() {
			    public void run() {
			    	// Wait a few ms to make sure the view is ready. LP
			    	try{ sleep(100); }catch(Exception e){ e.printStackTrace(); }
			    	overrideUrl(webview, url);
		       }
		    };
		    t.start();
			return true;
		}
		
		Log.d(getClass().getSimpleName(), "Loading normal");
		Toast.makeText(activity, "Loading Normal", Toast.LENGTH_SHORT).show();
		/*history.addFirst(new HistoryContainer(urlString, null));
		return false;*/
		// Send external links to an external browser. LP
		Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlString));
		activity.startActivity(myIntent);
		return true;
	}
	
	private void overrideUrl(WebView view, String urlString){
		BufferedReader inReader = null;
		try{
			Log.d(getClass().getSimpleName(), "Running Override Thread");
			CleanerProperties props = new CleanerProperties();
			props.setOmitComments(true);
			props.setAllowHtmlInsideAttributes(true);
			
			URL url = new URL(urlString);
			contextRoot = url.getProtocol()+"://"+url.getHost()+"/";
			URLConnection conn = url.openConnection();
			
			if(activity != null){
				Display dis = activity.getWindowManager().getDefaultDisplay();
				width = dis.getWidth();
			}
			
			inReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			setProgressMessage(Constants.PROGRESS_CONNECTED_WV);

			HtmlCleaner cleaner = new HtmlCleaner(props);
			TagNode source = cleaner.clean(inReader);
			inReader.close();

			TagNode body = source.findElementByName("body", false);
			setProgressMessage(Constants.PROGRESS_DOWNLOADED_WV);

			// Modifying the page via jQuery turned out to be too slow.  So we're instead modifying the
			// html before it's rendered. 20110406LP
			parseChildElements(body);
			TagNode js = new TagNode("script");
			js.setAttribute("language", "text/javascript");
			cleaner.setInnerHtml(js, "$(document).ready(function(){ $('.newsText img').each(function(){ $(this).css('width','50%'); $(this).css('height','50%');}); });");
			body.addChild(js);
			setProgressMessage(Constants.PROGRESS_PARSED_WV);
			
			// Append out injected javascript right before the closing HEAD tag. LP
			/*List<Element> head = source.getAllElements(HTMLElementName.HEAD);
			EndTag end;
			if(!head.isEmpty()){
				// There should only ever be one HEAD element. LP
				end = head.get(0).getEndTag();
			}else{
				// If head is missing use body. LP
				List<Element> body = source.getAllElements(HTMLElementName.BODY);
				// If body is also missing then something is wrong with the page. LP
				if(body.isEmpty()) return false;
				end = body.get(0).getEndTag();
			}
			outHTML.replace(end, html.toString());*/
			PrettyHtmlSerializer serializer = new PrettyHtmlSerializer(props);
			StringWriter outHtml = new StringWriter();
			source.serialize(serializer, outHtml);
			lastHtml = outHtml.toString();
			setProgressMessage(Constants.PROGRESS_RENDERING_WV);
			
			HistoryContainer finishedHtml = new HistoryContainer(urlString, outHtml.toString());
			history.addFirst(finishedHtml);
			
			// Rendering will wait until images are loaded, this can take a long time on
			// slow connections so this is setting a time limit on the ProgressDialog. LP
			Thread t = new Thread() {
			    public void run() {
			    	// Wait a few ms to make sure the view is ready. LP
			    	try{ sleep(PROGRESS_TIMEOUT_MS); }catch(Exception e){ e.printStackTrace(); }
			    	endProgress();
		       }
		    };
		    t.start();
		    
		    hasBeenOverridden = true;
			view.loadDataWithBaseURL(urlString, finishedHtml.page, 
					null, Constants.HTML_ENCODING, urlString);
		}catch(SocketException se){
			Log.e(getClass().getSimpleName(), "Error downloading page.");
			se.printStackTrace();
			endProgress();
			Toast.makeText(activity, "Could not connect to server.  Please try again later.", Toast.LENGTH_LONG).show();
		}catch(IOException ioe){
			Log.e(this.getClass().getSimpleName(), "An unexpected error occured.");
			ioe.printStackTrace();
			endProgress();
			Toast.makeText(activity, "Unexpected error.", Toast.LENGTH_LONG).show();
		}finally{
			if(inReader != null)
				try{ inReader.close(); }catch(Exception e){}
		}
	}
	
	/**
	 * Recursive function for modifying HTML element.  This function also decides 
	 * which elements should be kept and which are removed.
	 * @param e The current Element to parse.
	 * @return short value defining if the current Element will be kept. -1 if false, 1 if true and 
	 * 0 if not applicable. (0 is mainly for tags we aren't processing.)
	 * A value of 1 will be propagated to all parent Elements.
	 */
	private short parseChildElements(TagNode e)
	{
		// We don't want to have to process _everything_ like a and img tags unless we want to. LP
		String tagType = e.getName();
		// Remove comments. LP
		if(tagType.equals("!--")) return REMOVE;
		
		short keep = (tagType.equals("div") || tagType.equals("span")) ? REMOVE : UNKNOWN;
//		System.out.println("Target tag type: "+tagType+" default value "+keep);
		
		// Extract Attributes in case we want to replace them. LP
		Map<String, String> attrMap = e.getAttributes();
		boolean replaceAttrs = false;
		
		String id = (attrMap.containsKey("id")) ? attrMap.get("id").toLowerCase() : "";
		String styleClass = (attrMap.containsKey("class")) ? attrMap.get("class").toLowerCase() : "";
		String style = attrMap.containsKey("style") ? attrMap.get("style").toLowerCase() : "";
		
		boolean debugTag = false;
		
		// Process tag id's
		if("header".equals(id)){
			keep = KEEP;
		}else if("flashheader".equals(id)){
			style += " height: 0px; ";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = REMOVE;
		}else if("navbar".equals(id)){
			keep = KEEP;
		}else if ("poll".equals(id)){
			keep = KEEP;
		}else if ("headercontent".equals(id)){
			style += " width: 100%; ";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if ("maincontentfull".equals(id)){
			style += " margin: 0px; width: 100%; ";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if ("mainbody".equals(id)){
			style += " margin: 0px; width: 100%; background-image: none;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if("page".equals(id)){
			if(style.contains("min-width")){
				int start = style.indexOf("min-width");
				int end = style.indexOf(";", start);
				style = style.substring(0, start-1)+style.substring(end+1);
			}
			style += " background: none; min-height: 100px; height: 100%; ";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if("nice-menu-1".equals(id)){
			style += " left: 0px; top: 20px; float: none;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if("loginattop".equals(id)){
			keep = FORCE_REMOVE;
		}else if("logininfo".equals(id)){
			style += " display: block; visibility: hidden;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		
		else if("language-selector".equals(id)){
			style += " position: relative; display: block; float: none;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if("sidecontent".equals(id)){
			style += " display: none;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = REMOVE;
		}else if("subheadwrap".equals(id)){
			style += " height: 0px; ";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = REMOVE;
		}else if("interiorcontenttop".equals(id)){
			style += " height: 35px; background-image: none; width: 100%";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = REMOVE;
		}/*else if("gamesystemsheader".equals(id)){
			keep = 1;
		}*/else if("gamesystemsbreadcrumb".equals(id)){
			keep = KEEP;
		}else if("gamesystemscontent".equals(id)){
			keep = KEEP;
		}else if("gamesystemsmainbox".equals(id)){
			keep = KEEP;
		}else if("gamesystemsmain".equals(id)){
			keep = KEEP;
		}else if("page".equals(id)){
			style += " min-width: 0px; ";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}else if("classintrotext".equals(id)){
			keep = KEEP;
		}else if("classintropad".equals(id)){
			keep = KEEP;
		}else if("classrepublic".equals(id)){
			keep = KEEP;
		}else if("classempire".equals(id)){
			keep = KEEP;
		}else if("classpanels".equals(id)){
			keep = KEEP;
		}else if(id.contains("gameinfo")){
			keep = KEEP;
		}else if("secondaryheader".equals(id)){
			debugTag = true;
		}
		
		if(styleClass.contains("secondaryheader")){
			style += " position: relative; width: 100%; left: 0px;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			debugTag = true;
		}
		
		// Process tag classes
		// We always use contains here because multiple classes can be defined. LP
		// I'm gonna try to remove registration to see how I like the layout. 20110414LP
		/*if(styleClass.contains("registration")){
			style += " max-width: "+width+"px; display: inline; left: 0px;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = 1;
		}*/
		if(styleClass.contains("registration")){
			style += " display: none;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = FORCE_REMOVE;
		}
		if(styleClass.contains("forumsub")){
			style += " margin: 0px;";
			attrMap.put("style", style);
			
//			if(styleClass.matches("odd.+forumsub")){
//				styleClass.replace("odd  forumsub", "even forumSub");
				// TODO Get Regex working.
//			Log.d(this.getClass().getSimpleName(), "|"+styleClass+"|");
//				styleClass.replaceAll("odd.+forumsub", "even forumSub");
				styleClass = "even forumSub";
				attrMap.put("class", styleClass);
//			}
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("maincontentfulltop")){
			style += " background-image: none; width: 100%; left: 0px;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("maincontentfulltitle")){
			style += " width: 100%; bottom: 10px; position: relative; left: 0px;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("maincontentfullbody")){
			// Setting width to specific size because of header text in forums. 20110503LP
			style += " background-image: none; width: "+width+"px;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("maincontentbody")){
			style += " width: 100%; background-image: none;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = KEEP;
		}
		// I'm gonna try removing comments to see how I like it. 20110414LP
		/*if(styleClass.contains("maincontentcommentheader")){
			style += " background-image: none; width: 100%;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = 1;
		}*/
		if(styleClass.contains("maincontentcommentheader")){
			style += " display: none;";
			attrMap.put("style", style);
			replaceAttrs = true;
			keep = REMOVE;
		}
		if(styleClass.contains("contentpadding")){
			keep = KEEP;
		}
		if(styleClass.contains("localization")){
			keep = KEEP;
		}
		if(styleClass.contains("navbar")){
			keep = KEEP;
		}
		if(styleClass.contains("thumbbackground")){
			keep = KEEP;
		}
		if(styleClass.contains("resulttitle")){
			keep = KEEP;
		}
		if(styleClass.contains("resulttext")){
			keep = KEEP;
		}
		if(styleClass.contains("resultsubtext")){
			keep = KEEP;
		}
		if(styleClass.contains("thumbbackground")){
			keep = KEEP;
		}
		// I'm gonna try removing comments to see how I like it. 20110414LP
		/*if(styleClass.contains("comment")){
			attrMap.put("width", "100%");
			replaceAttrs = true;
			keep = 1;
		}*/
		if(styleClass.contains("comment")){
			keep = FORCE_REMOVE;
		}
		if(styleClass.contains("newstext")){
			keep = KEEP;
		}
		if(styleClass.contains("contentpadding")){
			attrMap.put("margin", "0");
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("resultcategory")){
			keep = KEEP;
		}
		if(styleClass.contains("resultsubtext")){
			keep = KEEP;
		}
		if(styleClass.contains("interiortitle")){
			style += " position: relative; height: 77px; width: 100%;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("skills")){
			keep = KEEP;
		}
		if(styleClass.contains("shared")){
			keep = KEEP;
		}
		if(styleClass.contains("sharedskills")){
			style += " float: none;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("screenshots")){
			style += " float: none; width: 100%;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("articletags")){
			style += " background-image: none; width: 100%;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		// I'm gonna try to remove registration to see how I like the layout. 20110414LP
		if(styleClass.contains("registration")){
			if(style.contains("display")){
				int ds = style.indexOf("display");
				int de = style.indexOf(";", ds);
				style = style.substring(0, ds-1)+style.substring(de+1);
			}
//			style += " position: relative; padding-top: 7px; padding-bottom: 7px; display: block; align: left;";
			// Do NOT convert to block: none;  We need to be able to interact with the html block. LP
			style += " visibility: hidden;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		// Different styles are used in different sections of the site. LP
		if(id.equals("loginerrormessage") || styleClass.contains("logininvalidpassword")){
			// Do NOT convert to block: none;  We need to be able to interact with the html block. LP
			style += " display: block; visibility: hidden;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		if(styleClass.contains("loginforgotpassword")){
			// Do NOT convert to block: none;  We need to be able to interact with the html block. LP
			style += " visibility: hidden;";
			attrMap.put("style", style);
			
			replaceAttrs = true;
			keep = KEEP;
		}
		
		if(!replaceAttrs){
			try{
				String width = attrMap.containsKey("width") ? attrMap.get("width").toLowerCase() : "";
				if(Integer.parseInt(width) > this.width){
					attrMap.put("width", "100%");
					replaceAttrs = true;
				}
			}catch(Exception ex){}
		}
		
		// We probably want to keep all forms. LP
		if(tagType.equals("form")){
//			Log.d(this.getClass().getSimpleName(), "Default to 1");
			keep = KEEP;
		}
		if(tagType.equals("a")){
			String href = attrMap.containsKey("href") ? attrMap.get("href") : "";
			if(href.length() > 0){
				href = Utils.fullUrl(contextRoot, href);
				if(href.lastIndexOf("/") == href.length()-1)
					href = href.substring(0, href.length() - 1);
				e.setAttribute("href", Utils.fullUrl(contextRoot, href));
			}
		}
		if(tagType.equals("img")){
			String src = attrMap.containsKey("src") ? attrMap.get("src") : "";
			if(src.length() > 0){
				src = Utils.fullUrl(contextRoot, src);
				e.setAttribute("src", src);
			}
		}
		if(tagType.equals("object")){
			keep = REPLACE;
		}
		if(tagType.equals("table")){
			// I'm not sure I want to keep this, but interface components tend to be in tables that 
			// are too large. LP
			if(attrMap.containsKey("width")){
				e.setAttribute("width", "");
			}
		}
		if(tagType.equals("input")){
			// Making login elements more usable on a touchscreen interface. LP
			if(styleClass.contains("username")){
				style += " background-size: 150%;";
				attrMap.put("style", style);
				
				replaceAttrs = true;
				keep = 1;
			}else if(styleClass.contains("password")){
				style += " background-size: 150%;";
				attrMap.put("style", style);
				
				replaceAttrs = true;
				keep = 1;
			}
		}
			
		// If we can't identify the tag then we may not want to remove it.
		// Let other Elements decide it's fate. LP
		// TODO Verify this is true.
		/*if(keep == -1 && "".equals(id) && "".equals(styleClass)){
			System.out.println("Default to 0");
			keep = 0;
		}*/
		
		
		if(replaceAttrs){
			// TODO May be unnecessary. Need to test.
			for(String key : attrMap.keySet()){
				e.setAttribute(key, attrMap.get(key));
			}
		}
		
		// TODO Another thing to review.  I'm using specialTags to keep track of
		// conditional tags I want to remove. 20110503LP
		if(keep != 1 && (specialTags.contains(id) || specialTags.contains(styleClass))){
			keep = REMOVE;
		}
		
		if(debugTag){
			if(!GeneralUtils.isEmpty(id))
				Log.d(getClass().getSimpleName(), "id="+id);
			if(!GeneralUtils.isEmpty(styleClass))
				Log.d(getClass().getSimpleName(), "style="+style);
			for(String key : attrMap.keySet())
				Log.d(getClass().getSimpleName(), key+"=>"+attrMap.get(key));
		}
		
		// Now lets actually remove elements we don't want from the html. LP
		if(keep != -2 && e.hasChildren()){
			List children = e.getChildren();
			ArrayList<TagReplacement> replacements = null;
			for(Iterator i = children.iterator(); i.hasNext();){
				Object o = i.next();
				if(!(o instanceof TagNode)) continue;
				TagNode child = (TagNode)o;
					
				short keepChild = UNKNOWN; 
				try{
					keepChild = parseChildElements(child);
				}catch(StackOverflowError soe){
					soe.printStackTrace();
					Log.e(this.getClass().getSimpleName(), "Stack Overflow parsing HTML");
				}
				if(keepChild <= REMOVE){
					i.remove();
				}else if(keepChild == KEEP){
					keep = KEEP;
				}else if(keepChild == REPLACE){
					if(replacements == null) replacements = new ArrayList<TagReplacement>();
					ContentNode msg = new ContentNode(Constants.HTML_FLASH_REMOVED);
					replacements.add(new TagReplacement(child, msg));
				}// If keepChild == 0 then do nothing. LP
				
				// insertChild methods alter the List being used in this loop,
				// so the new elements need to be temporarily stored to avoid
				// ConncurrentModificationExceptions. 20110505LP
				/*if(keepChild == REPLACE){
					if(newNodes == null) newNodes = new HashMap<Integer, TagNode>();
					TagNode msg = new TagNode("div");
					msg.setAttribute("id", "replaceMessage");
					msg.setAttribute("style", "border: 1px;");
					msg.getText().append(REPLACE_MESSAGE);
					newNodes.put(cnt, msg);
				}*/
			}
			if(replacements != null)
			 for(TagReplacement rep : replacements)
				e.replaceChild(rep.oldTag, rep.newTag);
		}
		
//		if(keep <= REMOVE) Log.d(this.getClass().getSimpleName(), 
//				"Removing "+tagType+" id:"+id+" class:"+styleClass);
		
		return keep;
	}

	@Override
	public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
		Log.w(this.getClass().getSimpleName(), "Error loading: "+failingUrl+" "+errorCode+":"+description);
		if(activity != null)
		   Toast.makeText(activity, "An error occured: " + description, Toast.LENGTH_LONG).show();
	}
	
	@Override
	public void onUnhandledKeyEvent(WebView view, KeyEvent event){
		if(event.getKeyCode() == KeyEvent.KEYCODE_BACK){
			Log.d(this.getClass().getSimpleName(), "Unhandled back press");
		}
		Log.d(getClass().getSimpleName(), "Unhandled button press");
		super.onUnhandledKeyEvent(view, event);
	}
	
	@Override
	public void onLoadResource(WebView view, String url){
//		Log.d(this.getClass().getSimpleName(), "Reviewing resource: "+url);
		// Replace images we don't need or are too large for mobile layouts. LP
		if(url.matches("files.*avatars.*[gif|jpg|png]")){
			Log.d(getClass().getSimpleName(), "Skipping resource: "+url);
			return;
		}

//		Log.d(this.getClass().getSimpleName(), "Loading resource: "+url);
		super.onLoadResource(view, url);
	}
	
	@Override
	public void onPageStarted(WebView view, String url, Bitmap favicon){
		Log.d(getClass().getSimpleName(), "onPageStarted: "+url);
		startProgress();
		// If these don't match then WebView hasn't called shouldOverrideUrlLoading when it should have.
		// This usually occurs when pages are loaded via javascript.  LP
		if(shouldOverride(url) != hasBeenOverridden){
			Log.i(getClass().getSimpleName(), "Manually forcing page to override.");
			view.stopLoading();
			view.loadUrl(url);
		}
	}
	
	@Override
	public void onPageFinished(WebView view, String url){
		Log.d(getClass().getSimpleName(), "onPageFinished: "+url);
		hasBeenOverridden = false;
		endProgress();
	}
	
	private void startProgress(){
		if(activity == null) return;
		if(pd == null){
			pd = ProgressDialog.show(activity, Constants.PROGRESS_TEXT_WV, Constants.PROGRESS_SUBTEXT_WV, true, false);
		}
	}
	
	private void endProgress(){
		if(pd != null && pd.isShowing()){ pd.dismiss(); pd = null; }
	}
	
	private void setProgressMessage(String message){
		if(message == null) return;
		if(pd != null){
			Log.d(getClass().getSimpleName(), message);
			
			// This allows us the refresh the view while continuing the parse the html.
			// Without this the screen won't be redrawn until overrideUrl is complete. LP
			final String msg = message;
			activity.runOnUiThread(new Runnable() {
				public void run() {
					pd.setMessage(msg);
				}
			});
		}
	}
	
	/**
	 * Essentially does the same thing that WebView.goBack() would do, but uses custom page caching.
	 */
	public void goBack(){
		if(canGoBack()){
			// Remove the current page. LP
			history.removeFirst();
			// Get the last page, but don't remove from history. LP
			HistoryContainer lastPage = history.peek();
			
			if(lastPage.page != null)
				webview.loadDataWithBaseURL(lastPage.url, lastPage.page, 
					null, Constants.HTML_ENCODING, lastPage.url);
			// Load urls we don't want to override. LP
			else if(webview instanceof WebViewWrapper)
				((WebViewWrapper)webview).internalLoadUrl(lastPage.url);
			else webview.loadUrl(lastPage.url);
		}
	}

	/**
	 * Essentially does the same thing that WebView.canGoBack() would do, but uses custom page caching.
	 */
	public boolean canGoBack(){
		return history.size() > 1;
	}
		
	public void setActivity(Activity newActivity){
		this.activity = newActivity;
	}
	public Activity getActivity(){
		return this.activity;
	}

	public String getLastHtml() {
		return lastHtml;
	}
	public void setLastHtml(String lastHtml) {
		this.lastHtml = lastHtml;
	}

	public int getWidth() {
		return width;
	}
	public void setWidth(int width) {
		this.width = width;
	}

	public ProgressDialog getProgressDialog() {
		return pd;
	}
	public void setProgressDialog(ProgressDialog pd) {
		this.pd = pd;
	}
	
	private class HistoryContainer{
		public String url;
		public String page;
		
		public HistoryContainer(String url, String page){
			this.url = url;
			this.page = page;
		}
	}
	
	private class TagReplacement{
		public HtmlNode oldTag;
		public HtmlNode newTag;
		
		public TagReplacement(HtmlNode oldTag, HtmlNode newTag){
			this.oldTag = oldTag;
			this.newTag = newTag;
		}
	}
}